﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace NASA.BeAMartian.Execution
{
    public class ExecutionQueue
    {
        public static ExecutionQueue operator +(ExecutionQueue queue, IEnumerable<ExecutionItem> executionItems)
        {
            return queue.Enqueue(executionItems);
        }

        public static ExecutionQueue operator +(ExecutionQueue queue, ExecutionItem item)
        {
            return queue.Enqueue(item);
        }

        public static ExecutionQueue operator +(ExecutionQueue queue, Action action)
        {
            return queue.Enqueue(action);
        }

        /// <summary>
        /// Fired when completed, canceled, or error that caused the execution queue to stop.
        /// </summary>
        public event EventHandler Completed;

        private long executionCount = 0;

        private Queue<ExecutionItem> ExecutionItems { get; set; }

        private ExecutionItem currentActionItem = null;

        public ExecutionQueue()
        {
            this.ExecutionItems = new Queue<ExecutionItem>();
        }

        public bool IsRunning { get; private set; }

        public ExecutionQueue Enqueue(IEnumerable<ExecutionItem> executionItems)
        {
            foreach (ExecutionItem item in executionItems)
            {
                this.ExecutionItems.Enqueue(item);
            }
            return this;
        }

        public ExecutionQueue Enqueue(ExecutionItem executionItem)
        {
            this.ExecutionItems.Enqueue(executionItem);
            return this;
        }

        public ExecutionQueue Enqueue(Action action)
        {
            this.ExecutionItems.Enqueue(new ActionExecutionItem(action));
            return this;
        }

        public void Run()
        {
            if (this.IsCanceled)
                throw new InvalidOperationException("ExecutionQueue was canceled.");

            if (this.IsRunning)
                return;

            this.ProcessNext();
        }

        public void Cancel()
        {
            if (this.currentActionItem != null)
                this.currentActionItem.Cancel();

            while (this.ExecutionItems.Count > 0)
            {
                ExecutionItem item = this.ExecutionItems.Dequeue();
                item.Cancel();
            }

            this.IsCanceled = true;
        }

        public bool IsCanceled { get; private set; }

        public Exception Error { get; private set; }

        protected void OnCompleted()
        {
            if (this.Completed != null)
                this.Completed(this, EventArgs.Empty);
        }

        private void ProcessNext()
        {
            if (this.ExecutionItems != null && this.ExecutionItems.Count > 0 && this.IsCanceled == false)
            {
                this.IsRunning = true;

                this.currentActionItem = this.ExecutionItems.Dequeue();
                this.currentActionItem.ActionCompleted += new EventHandler<ExecutionItemCompletedEventArgs>(nextActionItem_ActionCompleted);
                this.executionCount += 1;

                if(System.Diagnostics.Debugger.IsAttached)
                {
                    string name = (string.IsNullOrEmpty(this.currentActionItem.Name) ? this.currentActionItem.GetType().Name : this.currentActionItem.Name);
                    Debug.WriteLine("Executing: [{0}] : {1}", this.executionCount, name); 
                }

                this.currentActionItem.Invoke();
            }
            else
            {
                this.IsRunning = false;
                this.currentActionItem = null;
                
                this.OnCompleted();
            }
        }

        private void nextActionItem_ActionCompleted(object sender, ExecutionItemCompletedEventArgs e)
        {
            ExecutionItem actionItem = (ExecutionItem)sender;
            this.currentActionItem = null;

            if (e.Error == null || actionItem.ContinueOnError == true)
            {
                this.ProcessNext();
            }
            else
            {
                this.IsRunning = false;
                this.Error = e.Error;
                this.OnCompleted();
            }
        }
    }
}